| |
6.3. Практика програмування PIC-мікроконтролерів
6.3.1. Опис лабораторного макета
Для того, щоб написати перші учбові програми і перевірити їх функціонування, бажано мати відносно нескладний макет, що містить найпоширеніші периферійні пристрої. Схема подібного макета, що використовується при виконанні лабораторних робіт студентами, приведена на мал. 6.3 .
Макет живиться від джерела стабілізованої напруги +5В. Тактова частота МК задається RC-ланцюгом і складає близько 2 Мгц. До лінії RA0 порту А підключений біполярний транзистор в ключовому режимі, навантажений на динамік ВА1. Звучання динаміка забезпечується подачею на вихід RA0 сигналу, що змінюється, в звуковому діапазоні. До лінії RA1 порту А підключений світлодіод VD2, що світиться при високій напрузі на виході. Тумблери SA1 і SA2, а також кнопки SB1 і SB2 підключені, відповідно, до ліній RA2 і RA3 порту А, а також до лінії RA4 порту А і лінії RB0 порту В. Вихідний стан кнопок - розімкнений, що забезпечує подачу на відповідні входи МК високого рівня сигналу.
Лінії RB1 - RB7 порту B обслуговують семисегментний індикатор HL1 із загальним анодом. Тому свічення сегменту індикатора забезпечується при низькому рівні сигналу на відповідному виході порту B. Макет також містить засоби програмування і зв'язку з комп'ютером, які на схемі не показані.

Мал. 6.3. Схема лабораторного макета.
6.3.2. Ініціалізація мікроконтролера макета
Перш ніж переходити до створення найпростіших призначених для користувача програм, необхідно описати змінні, що використовуються надалі, і набудувати МК на роботу з вибраним макетом. З цією метою ми напишемо і детально розглянемо лістинг початкової програми init.asm, до складу якої включатиметься вся решта програм користувача.
Лістинг 12.1. Програма init.asm
;******************************************************
;*листинг исходной программы
;******************************************************
LIST P=16C84, R=HEX ;директива, определяющая тип
;процессора и систему счисления
;по умолчанию
;******************************************************
;*описание используемых переменных и назначения адресов
;*ячеек для хранения переменных пользователя
;******************************************************
; INTCON EQU 0x0B
; OPTION EQU 0x81
; TMR0 EQU 0x01
; INTF EQU 1
; T0IF EQU 5
PCL EQU 0x02
STATUS EQU 0x03
RP0 EQU 5
PORTA EQU 0x05
PORTB EQU 0x06
TRISA EQU 0x05
TRISB EQU 0x06
W EQU 0
F EQU 1
TEMPA EQU 0x0C
TEMPB EQU 0x0D
COUNT1 EQU 0x0E
COUNT2 EQU 0x0F
COUNT3 EQU 0x10
;******************************************************
;*определение меток замены текста
;******************************************************
#DEFINE Z STATUS,2 ;бит нулевого результата
#DEFINE BA1 PORTA,0 ;динамик BA1
#DEFINE VD2 PORTA,1 ;светодиод VD2
#DEFINE SA1 PORTA,2 ;тумблер SA1
#DEFINE SA2 PORTA,3 ;тумблер SA2
#DEFINE SB1 PORTA,4 ;кнопка SB1
#DEFINE SB2 PORTB,0 ;кнопка SB2
#DEFINE HL1_A PORTB,1 ;индикатор-сегмент A
#DEFINE HL1_B PORTB,2 ;индикатор-сегмент B
#DEFINE HL1_C PORTB,3 ;индикатор-сегмент C
#DEFINE HL1_D PORTB,4 ;индикатор-сегмент D
#DEFINE HL1_E PORTB,5 ;индикатор-сегмент D
#DEFINE HL1_F PORTB,6 ;индикатор-сегмент E
#DEFINE HL1_G PORTB,7 ;индикатор-сегмент F
;******************************************************
;*исполняемая программа
;******************************************************
ORG 0x000 ;установка начального адреса по
;сбросу
GOTO BEGIN ;переход на начало программы
ORG 0x005 ;установка начального адреса
;размещения программы
BEGIN
CALL INIT_PORTS ;вызов подпрограммы
;инициализации портов МК
;*****************************************************
;*программа пользователя
;
;*****************************************************
;
INIT_PORTS ;подпрограмма инициализации
;портов
MOVLW 0xFF ;установка линий портов
MOVWF PORTA ;A и B в единичное
MOVWF PORTB ;состояние
BSF STATUS,RP0 ;переход на банк 1
MOVLW 0x1C ;настройка линий RA0 и
MOVWF TRISA ;RA1 порта A на вывод –
;остальных – на ввод
MOVLW 0x01 ;настройка линии RB0
MOVWF TRISB ;порта B на ввод -
;остальных – на вывод
BCF STATUS,RP0 ;возврат в банк 0
RETURN ;возврат из подпрограммы
;
END ;конец программы
Розглянемо роботу цієї програми. Спочатку вона указує асемблеру тип МК, що використовується, і систему числення за умовчанням. Асемблерні директиви EQU, що йдуть далі, визначають асемблерні константи, що використовуються в цій і подальших програмах. Вони дозволяють використовувати в тексті програми більш зручні мнемонічні мітки, прив'язані до структури конкретного МК, замість коректних, але складніших асемблерних виразів. Покажчики TEMPA, TEMPB, COUNT1 і COUNT2 призначають адреси елементів пам'яті для зберігання проміжних даних (поточних станів, змінних циклів і т.п.).
Асемблерні директиви #define задають рядок, що заміщає відповідну мітку, кожного разу, коли та зустрічатиметься в початковому тексті. В нашому випадку ці директиви дозволяють використовувати символічні імена, прив'язані до схеми макета, замість фізичних адрес відповідних розрядів портів і регістрів. При цьому необхідно мати на увазі, що символи, які визначені директивою #DEFINE, не можуть бути проглянуті симулятором. Тому для перегляду необхідно використовувати фізичні адреси портів і регістрів.
Директива ORG 0x00 встановлює стартову адресу програмного коду рівним 0, тобто відповідним початковому стану лічильника команд МК після скидання. Команда GOTO BEGIN разом з асемблерною директивою ORG 0x005 і міткою BEGIN забезпечують перехід на адресу пам'яті програм 0x005, починаючи з якої і розміщується основна частина програми. Це необхідне для того, щоб обійти адресу 0x004, що використовується як вектор переривання, і тим самим зарезервувати його для можливих майбутніх вживань.
Потім за допомогою команди CALL INIT_PORTS проводиться виклик підпрограми ініціалізації портів. Спочатку підпрограма ініціалізації встановлює у високий (одиничний) стан вихідні трігери даних. Ця операція рекомендується розробником МК для того, щоб виключити невизначеність в станах регістрів портів. Потім командою BSF STATUS,RP0 проводиться перемикання на банк 1 пам'яті даних, де розташовані регістри управління напрямом передачі інформації TRISA і TRISB. За допомогою команд MOVLW 0x1C і MOVWF TRISA лінії RA0 і RA1 порту А настроюються на вивід, а інші - на введення. Команди MOVLW 0x01 і MOVWF TRISB настроюють лінію RB0 порту B на введення, а інші - на виведення. За допомогою команди BCF STATUS,RP0 проводиться повернення в банк 0, де розташовуються необхідні для роботи програми регістри і порти.
Оскільки в процесі роботи з макетом перенастроювання портів не проводиться, і введених змінних достатньо для роботи всіх даних учбових задач, вони далі розглядатимуться включеними за умовчанням до складу початкової програми init.asm. При написанні учбових задач по можливості використовуватиметься метод структурного програмування, при якому прикладна програма будується з деякого набору програмних модулів, кожний з яких реалізує певну процедуру обробки даних. При цьому кожний з програмних модулів має тільки одну точку входу і одну точку виходу. Введені одного разу програмні модулі можуть використовуватися під своїм ім'ям в інших прикладних програмах.
6.3.3. Програмування учбових задач
Почнемо програмування учбових задач з написання програми, яка прочитує стан кнопки SB1 і виводить його на світлодіодний індикатор VD2 так, що не натискнутому стану кнопки (високому рівню сигналу на вході RA4) відповідає стан світлодіода, що світиться, і навпаки.
Лістинг 12.2.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RA ;вызов подпрограммы GET_RA
CALL SB1_VD2 ;вызов подпрограммы SB1_VD2
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
GET_RA ;подпрограмма чтения состояния
;порта A
MOVF PORTA,W ;чтение состояния порта A в W
MOVWF TEMPA ;пересылка W в TEMPA
RETURN ;возврат из подпрограммы
;
SB1_VD2 ;подпрограмма вывода на светодиод
;VD2 состояния кнопки SB1 (разряда 4
;регистра TEMPA)
BTFSS TEMPA,4 ;пропустить команду, если
;TEMPA,4=1 (кнопка не нажата)
GOTO P0 ;перейти на P0
BSF VD2 ;зажечь светодиод VD2
P0
BTFSC TEMPA,4 ;пропустить команду, если
;TEMPA,4=0 (кнопка нажата)
GOTO P1 ;перейти на P1
BCF VD2 ;погасить светодиод
P1
RETURN
;
Основна програма містить замкнутий цикл LOOP - GOTO LOOP, необхідний для періодичного повторення циклу контролю стану кнопки і виводу його на індикатор. Команда CLRWDT виключає вплив можливого скидання по переповнюванню сторожового таймера на роботу програми. Дві наступні команди здійснюють виклик підпрограм GET_RA і SB1_VD2. Перша з них (GET_RA) спочатку прочитує поточний стан порту А, яке поміщається в робочий регістр W. Оскільки робочий регістр може бути потрібен при виконанні інших команд, його стан записується в регістр TEMPA, що використовується тут для тимчасового зберігання стану порту А. Таким чином, після повернення з підпрограми GET_RA в розряді 4 регістри TEMPA міститься інформація про стан кнопки SB1: «1» - не натиснена, «0» - натиснена.
Підпрограма SB1_VD2 аналізує стан розряду 4 регістри TEMPA і, залежно від нього, запалює або гасить світлодіод. В системі команд МК PIC16F84 немає команд умовного переходу, тому для організації перевірки тієї або іншої умови використовуються команди, що дозволяють пропустити виконання наступної команди програми, залежно від стану певного біта в заданому регістрі (BTFSS і BTFSC). Зокрема, команда BTFSS TEMP,4 пропускає виконання команди GOTO P0, якщо TEMP,4 = 1 (кнопка не натиснена). Тим самим реалізується команда BSF VD2, яка запалює світлодіод VD2. Потім аналізується умова TEMP,4 = 0 (кнопка натиснена) і, якщо воно має місце, світлодіод гаситься.
Можлива більш проста реалізація заданого алгоритму, оскільки натиснений стан кнопки виключає не натиснений (і навпаки), але представлений варіант більш наочний.
Розглянемо складніший варіант програми, що передбачає запалення світлодіода VD2 тільки при наступному стані тумблерів і кнопок макета: SA1 = 1, SA2 = 1, SB1 = 1 і SB2 = 0.
Лістинг 12.3.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RA ;вызов подпрограммы GET_RA
CALL GET_RB ;вызов подпрограммы GET_RB
CALL ZAG_1110 ;вызов подпрограммы ZAG_1110
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
GET_RB ;подпрограмма чтения состояния
;порта B
MOVF PORTB,W ;чтение состояния порта B в W
MOVWF TEMPB ;пересылка W в TEMPB
RETURN
;
ZAG_1110 ;зажигает светодиод VD2 только при
;следующем состоянии тумблеров и
;кнопок макета:
;SA1 = SA2 = SB1 = 1 и SB2 = 0
BTFSS TEMPA,2 ;пропустить команду, если
GOTO P0 ;TEMPA,2=1
BTFSS TEMPA,3 ;пропустить команду, если
GOTO P0 ;TEMPA,3=1
BTFSS TEMPA,4 ;пропустить команду, если
GOTO P0 ;TEMPA,4=1
BTFSC TEMPB,0 ;пропустить команду, если
GOTO P0 ;TEMPB,0=0
BSF VD2 ;зажечь светодиод VD2
GOTO P1
P0
BCF VD2 ;погасить светодиод VD2
P1
RETURN
;
INCLUDE GET_RA.ASM
;
Підпрограми GET_RA і GET_RB поміщають в регістри TEMPA і TEMPB поточні стани портів А і B, відповідно. Підпрограма ZAG_1110 аналізує стани розрядів 2,3 і 4 регістри TEMPA і розряд 0 регістра TEMPB, і за умови TEMPA,2,3,4 = 1,1,1 і TEMPB,0 = 0, запалює світлодіод VD2. При невиконанні хоча б однієї з цих умов світлодіод гаситься.
Використовування директиви INCLUDE GET_PORTA.ASM дозволяє включати вже відладжені модулі підпрограм в поточну програму. Для того, щоб цією можливістю можна було скористатися, необхідно зберігати відладжені модулі у вигляді окремих асемблерних файлів.
Спробуємо тепер використовувати семисегментний індикатор для контролю стану тумблерів макета. Спочатку напишемо програму, яка виводить на індикатор HL семисегментне зображення будь-якого двійкового числа від 0b до 1111b в шістнадцятковому уявленні.
Лістинг 12.4.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
MOVLW 0x0A ;пересылка константы 0A в W
CALL SEV_SEG ;вызов подпрограммы SEVEN_SEG
MOVWF PORTB ;пересылка W в PORTB
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
SEV_SEG ;подпрограмма обслуживания
;семисегментного индикатора
ANDLW 0x0F ;маскирование 4-х младших разрядов
;W и обнуление 4-х старших
ADDWF PCL,F ;сложение W с PCL и пересылка
;результата в PCL
RETLW 0x80 ;возврат из подпрограммы с 80 в W
RETLW 0xF2 ;возврат из подпрограммы с F2 в W
RETLW 0x48 ;возврат из подпрограммы с 48 в W
RETLW 0x60 ;возврат из подпрограммы с 60 в W
RETLW 0x32 ;возврат из подпрограммы с 32 в W
RETLW 0x25 ;возврат из подпрограммы с 25 в W
RETLW 0x04 ;возврат из подпрограммы с 04 в W
RETLW 0xF0 ;возврат из подпрограммы с F0 в W
RETLW 0x00 ;возврат из подпрограммы с 00 в W
RETLW 0x20 ;возврат из подпрограммы с 20 в W
RETLW 0x10 ;возврат из подпрограммы с 10 в W
RETLW 0x06 ;возврат из подпрограммы с 06 в W
RETLW 0x8C ;возврат из подпрограммы с 8C в W
RETLW 0x42 ;возврат из подпрограммы с 42 в W
RETLW 0x0C ;возврат из подпрограммы с 0C в W
RETLW 0x1C ;возврат из подпрограммы с 1C в W
;
Програма починає свою роботу з пересилки константи 0x0A в робочий регістр W. Потім проводиться виклик підпрограми обслуговування семисегментного індикатора SEV_SEG. Робота підпрограми SEV_SEG починається з маскування 4-х молодших розрядів W і обнулення 4-х старших. Тим самим з аналізу виключаються старші розряди передаваного з робочого регістра W числа. Потім маскований вміст регістра W додається до поточного стану молодшого байта лічильника команд PCL, і результат поміщається в PCL. Таким чином, проводиться додатковий зсув лічильника команд на величину, яка була передана в робочому регістрі. Наприклад, якщо було W=0, то вміст лічильника команд не зміниться, і буде виконана наступна команда RETLW 0x80, яка викличе повернення з підпрограми із записом 0x80 = B'1000000' в регістр W. Якщо, як було в при введеній програмі, W=0A, то до вмісту PCL буде додано число 0x0A, і відбудеться додатковий зсув на 10 кроків. В результаті буде виконана команда RETLW 0x10, яка викличе повернення з підпрограми із записом 0x10 = B'0001000' в регістр W.
Після повернення з підпрограми проводиться пересилка W в PORTB і відображення його стану на семисегментному індикаторі HL. Зокрема, якщо W = 0, то при виводі 1000000b на порт B семисегментний індикатор покаже 0, а при W = А покаже А. Таким чином, може бути відображено будь-яке 4-розрядне двійкове число.
Метод прямого управління лічильником команд, використаний в підпрограмі SEV_SEG, може застосовуватися для реалізації табличної конвертації чисел. При цьому необхідно мати на увазі, що даний метод не дозволяє конвертувати більше 256 значень в одній таблиці. Крім того, програма табличної конвертації повинна цілком розташовуватися усередині 256-байтного блоку щоб уникнути переповнювання молодшого байта лічильника команд.
Використовуючи підпрограму SEV_SEG, напишемо тепер програму, яка читає стани тумблерів SA1 і SA2 і виводить на індикатор відповідне число.
Лістинг 12.5.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RA ;вызов подпрограммы GET_RA
RRF TEMPA,F ;сдвиг вправо на один разряд
;через перенос
RRF TEMPA,W ;сдвиг вправо на один разряд
;через перенос
ANDLW 0x03 ;маска на два младших разряда
CALL SEV_SEG ;вызов подпрограммы SEVEN_SEG
MOVWF PORTB ;пересылка W в PORTB
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
INCLUDE GET_RA.ASM
INCLUDE SEV_SEG.ASM
;
Підпрограма GET_RA поміщає в регістр TEMPA поточний стан порту А. Таким чином, в розрядах 2 і 3 регістри TEMPA зберігається поточний стан тумблерів SA1 і SA2. Для того, щоб біти стани тумблерів зайняли позиції 0 і 1 регістра TEMPA, проводиться два зсуви вправо через перенесення, причому результат другого зсуву поміщається в регістр W. Потім накладається маска на два молодші розряди робочого регістра і проводиться виклик підпрограми SEV_SEG. Після виходу з підпрограми результат подається на порт B і відображається на індикаторі.
Розглянемо тепер програми, що працюють в реальному масштабі часу, тобто видаючі сигнали певної тривалості і частоти проходження, або що враховують тимчасові параметри вхідних сигналів. Основним елементом таких програм є підпрограма формування тимчасової затримки. Розглянемо один з можливих варіантів такої підпрограми з використанням програмних методів формування затримки, тобто без вживання вбудованого таймера.
Лістинг 12.6.
;основная программа
MOVLW 0xL ;пересылка константы H'L' в W
CALL DELAY ;вызов подпрограммы DELAY
;
DELAY ;подпрограмма формирования
;задержки времени
MOVWF COUNT1 ;загрузка W в регистр COUNT1
LOOPD
DECFSZ COUNT1,F ;декремент COUNT1
GOTO LOOPD ;повторение цикла H'L' раз
RETURN ;возврат из подпрограммы
;
Основна програма проводить виклик підпрограми DELAY з деякою константою L в робочому регістрі W, визначаючої число внутрішніх циклів підпрограми. Підпрограма DELAY починає свою роботу із завантаження вмісту робочого регістра в регістр користувача COUNT1. Команда DECFSZ COUNT1,F зменшує на одиницю вміст регістра COUNT1 і перевіряє його на рівність нулю. Нульовий стан регістра COUNT1 приводить до виходу з циклу і повернення з підпрограми. Для виконання кожного внутрішнього циклу потрібні три машинні цикли МК (1 цикл на виконання команди DECFSZ при ненульовому результаті і 2 цикли на кожну команду GOTO). Вихід з підпрограми DELAY зажадає 4-і цикли (2 цикли на виконання команди DECFSZ при нульовому результаті і 2 цикли на RETURN). Якщо додати до цього ще 4 цикли, необхідні для завантаження константи в робочий регістр, виклик підпрограми і завантаження регістра користувача COUNT1, то загальний час виконання підпрограми DELAY (затримка) складе
TD = 4 + 3*(L - 1)+ 4 = 5 + 3*L циклів
де L - константа, передана через робочий регістр в підпрограму DELAY.
При тактовій частоті fosc = 2МГц час циклу рівний tц = 2 мкс, тому при завантаженні L = H'00' = .0 максимальний формований інтервал часу складе 1,55 мс. Такий результат зв'язаний з тим, що команда DECFSZ спочатку декрементирует вміст регістра (H'00' - 1 = H'FF'), а потім вже аналізує результат.
Мінімальний формований інтервал часу складе за тих же умов 5 циклів або 10 мкс. Для отримання такого інтервалу необхідно перед викликом підпрограми DELAY завантажити в робочий регістр число 0x01.
Для розширення верхньої межі формованих тимчасових інтервалів, а також з метою підвищення зручності роботи з підпрограмою, можна додати в цикл LOOPD одну або декілька додаткових команд, як які частіше за все використовується команда NOP. Для прикладу розглянемо підпрограму формування затримки часу DELAY_C
Лістинг 12.7.
;
DELAY_C ;подпрограмма формирования
;задержки времени (вариант C)
MOVWF MOVWF COUNT1 ;загрузка W в регистр COUNT1
LOOPD
NOP ;пустая команда
DECFSZ COUNT1,F ;декремент COUNT1
GOTO LOOPD ;повторение цикла H'L' раз
RETURN ;возврат из подпрограммы
;
Загальний час виконання підпрограми DELAY_C, включаючи її виклик, складе
TD = 4 + 4*(L - 1)+ 4 = 4 + 4*L циклів.
При тактовій частоті fosc = 2МГц і завантаженню константи L = H'F9' = .249 формований інтервал часу складе рівно 2 мс. Зменшення константи на одиницю зменшує формований часовий інтервал на 8 мкс. Зокрема, при L = .124 утворюється затримка в 1 мс.
Для формування великих затримок часу, що лежать в діапазоні десятих і одиниць секунд, такий підхід незручний. В цьому випадку використовуються вкладені цикли, як показано в наступному прикладі.
Лістинг 12.8.
;основная программа
MOVLW 0xL ;пересылка константы H'L' в W
CALL DELAY_D ;вызов подпрограммы DELAY_D
;
DELAY_D ;подпрограмма формирования
;большой задержки времени (вариант D)
MOVWF COUNT2 ;загрузка W в регистр COUNT2
CLRF COUNT1 ;сброс содержимого регистра COUNT1
LOOPD
DECFSZ COUNT1,F ;декремент COUNT1
GOTO LOOPD ;повторение цикла 256 раз
CLRWDT ;сброс сторожевого таймера
DECFSZ COUNT2,F ;декремент COUNT2
GOTO LOOPD ;повторение цикла H'L' раз
RETURN ;возврат из подпрограммы
;
Час виконання внутрішнього циклу підпрограми DELAY_D складає 3*256 + 4 машинних циклів МК, тому загальна затримка складе
TD = 5 + (3*256 + 4)*L циклів.
При тактовій частоті fosc = 2МГц час циклу рівний tц = 2 мкс, тому при завантаженні L = H'00' = .0 максимальний формований інтервал часу складе близько 0,4 з.
Оскільки формований інтервал часу достатньо великий, в зовнішній цикл включена команда скидання сторожового таймера.
Інтервал часу 0,4 с не зовсім зручний для отримання затримок часу, кратних секунді, тому розглянемо ще один варіант підпрограми формування великих затримок часу з додатковою командою NOP у внутрішньому циклі.
Лістинг 12.9.
;
DELAY_E ;подпрограмма формирования
;большой задержки времени (вариант E)
MOVWF COUNT2 ;загрузка W в регистр COUNT2
CLRF COUNT1 ;сброс содержимого регистра COUNT1
LOOPD
NOP ;пустая команда
DECFSZ COUNT1,F ;декремент COUNT1
GOTO LOOPD ;повторение цикла 256 раз
CLRWDT ;сброс сторожевого таймера
DECFSZ COUNT2,F ;декремент COUNT2
GOTO LOOPD ;повторение цикла H'L' раз
RETURN ;возврат из подпрограммы
;
Час виконання внутрішнього циклу підпрограми DELAY_E складає 4*256 + 4 машинних циклів МК, тому загальна затримка складе
TD = 5 + (4*256 + 4)*L циклів.
При тактовій частоті fosc = 2МГц і при завантаженні L = H'F3' = .243 формований інтервал часу складе близько 0,5 с при погрішності не більш 0,2%. Якщо необхідна більш висока точність, можна вставити необхідну кількість порожніх операцій в зовнішній цикл формування затримки.
Розглянемо далі декілька програм з використанням підпрограм формування затримки часу. Почнемо з написання програми, яка подає звуковий сигнал на динамік BA1 при натисненні на кнопку SB1. Динамік звучатиме тільки в тому випадку, якщо на вихід RA0 буде поданий сигнал, що періодично змінюється. Для того, щоб звук був добре чутний, його частота повинна знаходитися поблизу максимуму чутності людського вуха. Виберемо частоту звучання рівної 1 КГц, що відповідає періоду проходження імпульсів сигналу 1 мс.
Лістинг 12.10.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RA ;вызов подпрограммы GET_PORTA
CALL SB1_BA1 ;вызов подпрограммы SB1_BA1
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
SB1_BA1 ;подпрограмма подачи звука на
;динамик BA1 при нажатии на кнопку
;SB1
BTFSC TEMPA,4 ;пропустить команду, если
;TEMPA,4=0 (кнопка нажата)
GOTO B0 ;перейти на B0
BSF BA1 ;подача высокого уровня на RA0
MOVLW 0x3E ;пересылка константы
;H'3E' = .62 в W
CALL DELAY_C ;вызов подпрограммы DELAY_C
BCF BA1 ;подача низкого уровня на RA0
MOVLW 0x3E ;пересылка константы
;H'3E' = .62 в W
CALL DELAY_C ;вызов подпрограммы DELAY_C
B0
RETURN
;
INCLUDE GET_RA.ASM
INCLUDE DELAY_C.ASM
;
Як і раніше, підпрограма GET_RA прочитує поточний стан порту А, яке потім передається в регістр TEMPA. Підпрограма SB1_BA1 аналізує стан розряду 4 регістри TEMPA і, залежно від результату, озвучує динамік BA1 чи ні. Необхідна витримка лінії RA0 в одиничному і нульовому станах забезпечується підпрограмою DELAY_C з параметром L = H'3E' = .62. Це відповідає часу затримки близько 0,5 мс, що і дає в результаті необхідну частоту проходження сигналу 1 Кгц.
Розглянемо далі програму, яка примушує мигати світлодіод VD2 при натисненні на кнопку SB1. Для того, щоб мигання були добре видні, виберемо їх частоту рівної 1 Гц.
Лістинг 12.11.
;основная программа
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RA ;вызов подпрограммы GET_RA
CALL SB1_VD2M ;вызов подпрограммы
;SB1_VD2M
GOTO LOOP ;переход к метке LOOP для
;повторения процесса
;
SB1_VD2M ;подпрограмма мигания светодиода
;VD2 при нажатии на кнопку SB1
BTFSC TEMPA,4 ;пропустить команду, если
;TEMPA,4=0 (кнопка нажата)
GOTO V0 ;перейти на V0
BSF VD2 ;зажечь светодиод VD2
MOVLW 0xF3 ;пересылка константы
;H'F3' = .243 в W
CALL DELAY_E ;вызов подпрограммы DELAY_E
BCF VD2 ;погасить светодиод
MOVLW 0xF3 ;пересылка константы
;H'F3' = .243 в W
CALL DELAY_E ;вызов подпрограммы DELAY_E
V0
BTFSS TEMPA,4 ;пропустить команду, если
;TEMPA,4=1 (кнопка не нажата)
GOTO V1 ;перейти на V1
BCF VD2 ;погасить светодиод
V1
RETURN
;
INCLUDE GET_RA.ASM
INCLUDE DELAY_E.ASM
;
Програма працює майже так само, як і попередня. Перша відмінність полягає в тому, що світлодіод примусово гаситься при не натисненій кнопці. Друга відмінність полягає у величині інтервалу часу, який складає тут 0,5 с і формується підпрограмою DELAY_E.
Підпрограми формування затримки часу можуть бути також корисні при роботі з такими зовнішніми джерелами сигналів, як тумблери, кнопки, перемикачі і т.п. Річ у тому, що всі механічні комутатори мають одну негативну властивість, відому як «брязкіт» контактів, яке обумовлене механічними коливаннями контактів при їх замиканні і розмиканні. Тривалість коливань складає звичайно декілька мілісекунд, протягом яких на вхід МК може поступати пачка імпульсів замість ідеального перепаду.
Апаратні способи боротьби з «брязкотом» контактів засновані на використовуванні RS-трігерів, одновібраторів або трігерів Шмітта. В пристроях на основі МК придушення «брязкоту» контактів звичайно здійснюється програмними способами, які засновані на повторному прочитуванні стану лінії порту через певний час.
Як приклад розглянемо варіант підпрограми читання стану порту А що немає проблеми брязкоту контактів.
Лістинг 12.12.
;
GET_RAD ;подпрограмма чтения состояния
;порта A в регистр TEMPA
;с подавлением "дребезжания"
DD
MOVF PORTA,W ;чтение состояния порта A в W
ANDLW 0x1C ;наложение маски b'00011100'
;на неиспользуемые биты W
MOVWF TEMPA ;пересылка W в TEMPA
CLRWDT ;сброс сторожевого таймера WDT
MOVLW 0x0A ;пересылка константы
;H'0A' = .10 в W
CALL DELAY_E ;вызов подпрограммы DELAY_E
MOVF PORTA,W ;чтение состояния порта A в W
ANDLW 0x1C ;наложение на W маски b'00011100'
SUBWF TEMPA,W ;вычитание W из TEMPA
BTFSS Z ;пропустить команду, если результат
;нулевой
GOTO DD ;перейти на метку DD
RETURN
;
INCLUDE DELAY_E.ASM
;
Суть роботи підпрограми полягає в повторному читанні стану порту А через деякий час після попереднього і порівнянні його з колишнім значенням. Константа H'0A' = .10, що пересилається в регістр W перед викликом підпрограми DELAY_E, забезпечує значення затримки часу близько 20 мс - цього, як правило, достатньо для завершення перехідних процесів при перемиканні механічних комутаторів. Маскування невживаних розрядів порту підвищує надійність роботи підпрограми. Скидання сторожового таймера перед викликом підпрограми затримки потрібне для виключення скидання МК між двома процедурами опиту порту А.
Розглянемо тепер роботу програми, яка використовує деякі з розроблених раніше підпрограм. Хай метою роботи програми є підрахунок числа натиснень на кнопку SB1 з висновком результату на семисегментний індикатор в шістнадцятковому коді.
Лістинг 12.13.
;основная программа
CLRF COUNT3 ;сброс счетчика нажатий
LOOP
CLRWDT ;сброс сторожевого таймера
CALL GET_RAD ;вызов подпрограммы GET_RAD
BTFSC TEMPA,4 ;проверка нажатия SB1
GOTO LOOP ;если не нажата – возврат
;на метку LOOP
INCF COUNT3,F ;инкремент счетчика
MOVF COUNT3,W ;пересылка содержимого
;счетчика в рабочий регистр
CALL SEV_SEG ;вызов подпрограммы SEVEN_SEG
MOVWF PORTB ;пересылка W в PORTB
TEST
CALL GET_RAD ;вызов подпрограммы GET_RAD
BTFSS TEMPA,4 ;проверка нажатия SB1
GOTO TEST ;если еще нажата – возврат
;на метку TEST
GOTO LOOP ;возврат на метку LOOP
;
INCLUDE GET_RAD.ASM
INCLUDE SEV_SEG.ASM
;
Приведені вище програми не охоплюють і малої частки можливостей, які надає навіть такий простий макет, як зображений на мал. 6.3. Проте їх освоєння буде корисним для початківців користувачів PIC-контролерів.
попередня тема наступна тема
|